Z01-04 前端常用-工具方法
[TOC]
基本
判断对象类型
- isObject(value):
返回:boolean
,判断value是否为一个对象(对象,数组,函数)
定义函数
js
/**
* 判断对象类型
* @param {any} val 需要判断类型的值
* @returns {boolean} 是否为对象类型
*/
function isObject(val) {
const type = typeof val
return val !== null && (type === 'object' || type === 'function')
}
测试函数
js
// 测试
// true: array, object, set, map
// false: string, number,boolean, undefined, null
console.log(isObject('tom'))
console.log(isObject(123))
console.log(isObject(['a', 'b', 'c']))
console.log(isObject({ name: 'jack' }))
console.log(isObject(true))
console.log(isObject(undefined))
console.log(isObject(null))
console.log(isObject(new Set()))
console.log(isObject(new Map()))
格式化
格式化-时间
- formatTime(timeStamp, format):
返回:format
,格式化时间
定义函数
js
/**
* @description 格式化时间
* @param {number} timeStamp 时间戳
* @param {string} format 输出的时间格式
* @returns {string} format 格式化后的时间
*/
function formatTime(timeStamp, format) {
const time = new Date(timeStamp)
// 正则和值匹配
const oDate = {
'y+': time.getFullYear(),
'M+': time.getMonth() + 1,
'd+': time.getDate(),
'h+': time.getHours(),
'm+': time.getMinutes(),
's+': time.getSeconds()
}
// for循环替换
for (const key in oDate) {
const keyRe = new RegExp(key)
if (keyRe.test(format)) {
const value = (oDate[key] + '').padStart(2, '0')
format = format.replace(keyRe, value)
}
}
return format
}
调用函数
js
// 测试
console.log(formatTime(1694589797757, 'yyyy-MM-dd hh:mm:ss')) // 2023-09-13 15:23:17
console.log(formatTime(1694589797757, 'yyyy/MM/dd hh:mm:ss')) // 2023/09/13 15:23:17
console.log(formatTime(1694589797757, 'hh:mm:ss')) // 15:23:17
console.log(formatTime(1694589797757, 'yyyy/MM/dd')) // 2023/09/13
格式化-歌词
- formatLyric(lyric):
返回:aLyricParsed
,格式化歌词
定义函数
js
/**
* @description 解析歌词:将字符格式的歌词解析成对象格式
* @param {string} lyric
* @returns {array} aLyricParsed 格式如下:[{time: 2313, content: '歌词'},...]
*/
function parseLyric(lyric) {
const aLyricParsed = []
// 通过\n分割字符
const aLyric = lyric.split('\n')
// 遍历数组
const re = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/
for (const lyricLine of aLyric) {
// 去除空元素
if (!lyricLine) continue // 不能写成 return
// 获取歌词时间
const aTime = lyricLine.match(re)
const milliSecond = aTime[3].length === 2 ? aTime[3] * 10 : aTime[3] * 1
const time = aTime[1] * 60 * 1000 + aTime[2] * 1000 + milliSecond
// 获取歌词内容
const content = lyricLine.replace(re, '').trim()
aLyricParsed.push({ time, content })
}
return aLyricParsed
}
问题:将 if (!lyricLine) continue
写成了 if (!lyricLine) return
,导致for循环后续代码没有执行
调用函数
js
const lyric =
'[00:00.000] 作词 : 许嵩\n[00:01.000] 作曲 : 许嵩\n[00:02.000] 编曲 : 许嵩\n[00:03.000] 制作人 : 许嵩\n[00:22.240]天空好想下雨\n[00:24.380]我好想住你隔壁\n[00:26.810]傻站在你家楼下\n[00:29.500]抬起头数乌云\n[00:31.160]如果场景里出现一架钢琴\n[00:33.640]我会唱歌给你听\n[00:35.900]哪怕好多盆水往下淋\n[00:41.060]夏天快要过去\n[00:43.340]请你少买冰淇淋\n[00:45.680]天凉就别穿短裙\n[00:47.830]别再那么淘气\n[00:50.060]如果有时不那么开心\n[00:52.470]我愿意将格洛米借给你\n[00:55.020]你其实明白我心意\n[00:58.290]为你唱这首歌没有什么风格\n[01:02.976]它仅仅代表着我想给你快乐\n[01:07.840]为你解冻冰河为你做一只扑火的飞蛾\n[01:12.998]没有什么事情是不值得\n[01:17.489]为你唱这首歌没有什么风格\n[01:21.998]它仅仅代表着我希望你快乐\n[01:26.688]为你辗转反侧为你放弃世界有何不可\n[01:32.328]夏末秋凉里带一点温热有换季的颜色\n[01:41.040]\n[01:57.908]天空好想下雨\n[01:59.378]我好想住你隔壁\n[02:02.296]傻站在你家楼下\n[02:03.846]抬起头数乌云\n[02:06.183]如果场景里出现一架钢琴\n[02:08.875]我会唱歌给你听\n[02:10.974]哪怕好多盆水往下淋\n[02:15.325]夏天快要过去\n[02:18.345]请你少买冰淇淋\n[02:21.484]天凉就别穿短裙\n[02:22.914]别再那么淘气\n[02:25.185]如果有时不那么开心\n[02:27.625]我愿意将格洛米借给你\n[02:30.015]你其实明白我心意\n[02:33.327]为你唱这首歌没有什么风格\n[02:37.976]它仅仅代表着我想给你快乐\n[02:42.835]为你解冻冰河为你做一只扑火的飞蛾\n[02:48.406]没有什么事情是不值得\n[02:52.416]为你唱这首歌没有什么风格\n[02:57.077]它仅仅代表着我希望你快乐\n[03:01.993]为你辗转反侧为你放弃世界有何不可\n[03:07.494]夏末秋凉里带一点温热\n[03:11.536]\n[03:20.924]为你解冻冰河为你做一只扑火的飞蛾\n[03:26.615]没有什么事情是不值得\n[03:30.525]为你唱这首歌没有什么风格\n[03:35.196]它仅仅代表着我希望你快乐\n[03:39.946]为你辗转反侧为你放弃世界有何不可\n[03:45.644]夏末秋凉里带一点温热有换季的颜色\n'
console.log(parseLyric(lyric))
手写
call
- mrcall(mrthis, ...args):
返回:void
,call绑定this方法
定义函数
js
/**
* call绑定this方法
* @param {object} mrthis this对象
* @param {...any} args 参数
*/
Function.prototype.mrcall = function (mrthis, ...args) {
mrthis = mrthis === undefined || mrthis === null ? window : Object(mrthis)
// mrthis.fn = this;
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
mrthis.fn(...args)
delete mrthis.fn
}
测试
js
function foo(name, age) {
console.log(this, name, age)
}
foo.mrcall({ name: 'Tom' }, 'Tom', 18)
apply
- mrapply(mrthis, args):
返回:void
,apply绑定this方法
定义函数
js
/**
* apply绑定this方法
* @param {object} mrthis this对象
* @param {any} args 参数数组
*/
Function.prototype.mrapply = function (mrthis, args) {
mrthis = mrthis === undefined || mrthis === null ? window : Object(mrthis)
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
mrthis.fn(...args)
delete mrthis.fn
}
测试
js
function foo(name, age) {
console.log(this, name, age)
}
foo.mrapply({ name: 'Jack' }, ['Jack', 20])
bind
- mrbind(mrthis, ...args):
返回:bindFn
,bind绑定this方法
定义函数
js
/**
* bind绑定this方法
* @param {object} mrthis this对象
* @param {...any} args 参数数组
* @returns
*/
Function.prototype.mrbind = function (mrthis, ...args) {
return (...moreArgs) => {
mrthis = mrthis === undefined || mrthis === null ? window : Object(mrthis)
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
const allArgs = [...args, ...moreArgs]
mrthis.fn(...allArgs)
delete mrthis.fn
}
}
测试
js
function foo(name, age, height) {
console.log(this, name, age, height)
}
const newFoo = foo.mrbind({ name: 'Tom' }, 'Tom', 18)
newFoo(1.88) // => {name: 'Tom', fn: ƒ} 'Tom' 18 1.88
防抖
- mrDebounce(fn, delay, immediate):
返回:_fnDebounce
,防抖函数
定义函数
js
/**
* 防抖函数
* 1. 基本实现
* 2. 优化:参数和this指向
* 3. 优化:取消功能
* 4. 优化:第一次立即执行
* 5. 优化:返回值-利用Promise实现异步返回值
* @param {function} fn 需要防抖的回调函数
* @param {number} delay 延迟时间
* @param {boolean} immediate 是否开启第一次立即执行
* @returns {function} _fnDebounce
*/
function mrDebounce(fn, delay, immediate = false) {
let timer = null
let isFirst = true
// 1. 基本实现
const _fnDebounce = function (...args) {
// 5. 返回值
return new Promise((resolve, reject) => {
try {
let res = null
if (timer) clearTimeout(timer)
// 3. 第一次立即执行
if (immediate && isFirst) {
res = fn.apply(this, args)
resolve(res)
isFirst = false
return
}
// 延迟执行
timer = setTimeout(() => {
// 2. 参数和this指向
res = fn.apply(this, args)
resolve(res)
timer = null
isFirst = true
}, delay)
} catch (err) {
reject(err)
}
})
}
// 4. 取消功能
_fnDebounce.cancel = function () {
if (timer) clearTimeout(timer)
timer = null
isFirst = true
}
return _fnDebounce
}
调用函数
html
<input type="text" class="input" />
<button class="cancel">取消</button>
js
// 测试
const eInput = document.querySelector('.input')
const eCancelBtn = document.querySelector('.cancel')
// 基本实现
let count = 0
const fnDebounce = mrDebounce(function (name, age, gender) {
// console.log(`发送了 ${++count} 次网络请求:`, this.value, e)
console.log(`发送了 ${++count} 次网络请求:`, name, age, gender)
return '哈哈哈'
}, 2000)
eInput.oninput = fnDebounce
// 取消功能
eCancelBtn.onclick = fnDebounce.cancel
// 返回值
fnDebounce('tom', 18, 'male').then((res) => {
console.log('res: ', res)
})
节流
- mrThrottle(fn, interval, {leading, trailing}):
返回:_throttleFn
,节流函数
定义函数
js
/**
* 节流函数
* 1. 基本实现
* 2. 优化:绑定参数和this
* 3. 优化:控制立即执行
* 4. 优化:控制最后一次执行
* 5. 优化:取消功能
* 6. 优化:返回值
* @param {function} fn 被节流的回调函数
* @param {number} interval 间隔时间
* @param {object} {leading, trailing} leading:是否开启立即执行, trailing:是否执行最后一次
* @returns {_throttleFn} 节流后的函数
**/
function mrThrottle(fn, interval, { leading = true, trailing = false } = {}) {
let startTime = 0
let timer = null
// 1. 基本实现
const _throttleFn = function (...args) {
return new Promise((resolve, reject) => {
try {
const nowTime = new Date().getTime()
// 3. 控制立即执行
if (!leading && startTime === 0) {
startTime = nowTime
}
const waitTime = interval - (nowTime - startTime)
if (waitTime <= 0) {
if (timer) clearTimeout(timer)
// 2. 绑定参数和this
const res = fn.apply(this, args)
resolve(res)
startTime = nowTime
timer = null
return
}
// 4. 控制最后一次执行
if (trailing && !timer) {
timer = setTimeout(function () {
const res = fn.apply(this, args)
resolve(res)
startTime = new Date().getTime()
timer = null
}, waitTime)
}
} catch (err) {
reject(err)
}
})
}
// 5. 取消功能
_throttleFn.cancel = function () {
if (timer) clearTimeout(timer)
}
return _throttleFn
}
测试
js
// 测试
const eInput = document.querySelector('.input')
const eCancelBtn = document.querySelector('.cancel')
let count = 0
const throttleFn = mrThrottle(
function (e) {
console.log(`发送了 ${++count} 次网络请求: `, this.value, e)
return '哈哈~'
},
3000,
{ leading: false, trailing: true }
)
// 测试:节流
eInput.oninput = throttleFn
// 测试:取消功能
eCancelBtn.onclick = throttleFn.cancel
// 测试:返回值
throttleFn('tom').then((res) => {
console.log('res: ', res)
})
深拷贝
- deepCopy(originValue, map):
返回:newObj
,深拷贝
定义函数
js
/**
* 深拷贝
* 1. 基本实现
* 2. 优化:区分数组和对象
* 3. 优化:处理其他类型-set
* 4. 优化:处理其他类型-map
* 5. 优化:处理其他类型-function
* 6. 优化:处理其他类型-symbol为值
* 7. 优化:处理其他类型-symbol为key
* 8. 优化:处理循环引用
* @param {object} originValue 原始值
* @param {map} map 保存新对象的map
* @returns {object} newObj 深拷贝后的对象
*/
function deepCopy(originValue, map = new WeakMap()) {
// 1. 基本实现
// 6. 处理其他类型-symbol为值
if (Object.prototype.toString.call(originValue) === '[object Symbol]') {
const newSymbol = Symbol(originValue.description)
return newSymbol
}
// 如果不是对象类型,直接返回原始值
if (!isObject(originValue)) {
return originValue
}
// 3. 处理其他类型-set
if (Object.prototype.toString.call(originValue) === '[object Set]') {
const newSet = new Set()
for (const setItem of originValue) {
newSet.add(deepCopy(setItem, map))
}
return newSet
}
// 4. 处理其他类型-map
if (Object.prototype.toString.call(originValue) === '[object Map]') {
const newMap = new Map()
for (const [mapKey, mapValue] of originValue) {
newMap.set(mapKey, mapValue)
}
return newMap
}
// 5. 处理其他类型-function
if (Object.prototype.toString.call(originValue) === '[object Function]') {
return originValue
}
// 如果是对象类型,则创建一个新对象
// 8. 处理循环引用
if (map.get(originValue)) {
return map.get(originValue)
}
// 2. 区分数组和对象
const newObj = Array.isArray(originValue) ? [] : {}
map.set(originValue, newObj)
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key], map)
}
// 7. 处理其他类型-symbol为key
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const symbolKey of symbolKeys) {
console.log(symbolKey)
newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map)
}
return newObj
}
测试
js
// 测试
const obj = {
name: 'tom',
age: 18,
friend: {
name: 'jack',
gender: 'male',
address: {
country: 'China',
city: 'Shanghai'
}
},
hobbies: ['football', 'tennis'],
set: new Set([111, 222, 333, { name: 'setItem' }]),
map: new Map([
['刘备', { country: '蜀', nowLocation: '四川' }],
['曹操', { country: '魏', nowLocation: '北方' }],
['孙权', { country: '吴', nowLocation: '江南' }]
]),
s1: Symbol('s1'),
[Symbol('s2')]: 's2'
}
obj['self'] = obj
const newObj = deepCopy(obj)
obj.friend.name = '张飞'
console.log(newObj)
console.log(newObj.friend.name)
console.log(deepCopy('name'))
事件总线
- MrEventBus:
返回:
,事件总线- on(eName, eFn):
返回:
,监听事件 - emit(eName, ...args):
返回:
,触发事件 - off(eName, eFn):
返回:
,取消事件
- on(eName, eFn):
定义类
js
/**
* 事件总线
* 1. 基本实现
* 2. 优化:绑定参数
* 3. 优化:off方法
*/
class MrEventBus {
constructor() {
this.eMap = {}
}
// 监听事件
on(eName, eFn) {
let aFn = this.eMap[eName]
if (!aFn) {
aFn = []
this.eMap[eName] = aFn
}
aFn.push(eFn)
}
// 触发事件
emit(eName, ...args) {
const aFn = this.eMap[eName]
if (!aFn) return
aFn.forEach((fn, i) => {
fn(...args)
})
}
// 取消事件
off(eName, eFn) {
const aFn = this.eMap[eName]
if (!aFn) return
aFn.forEach((fn, i) => {
if (fn === eFn) {
aFn.splice(i, 1)
return
}
})
// 如果aFn清空了
if (aFn.length === 0) {
delete this.eMap[eName]
}
}
}
测试
html
<button class="btn">触发</button>
js
// 测试
const elBtn = document.querySelector('.btn')
const eb = new MrEventBus()
const hdlClick = function (name, age) {
console.log('监听点击事件hdlClick~', name, age)
}
const hdlClick2 = function (name, age) {
console.log('监听点击事件hdlClick2~', name, age)
}
// 1. 监听事件
eb.on('mrclick', hdlClick)
eb.on('mrclick', hdlClick2)
// 2. 触发事件
// eb.emit('mrclick', 'tom', 18)
// 3. 取消事件:一开始可以触发2个事件,3秒后只能触发一个事件
setTimeout(function () {
eb.off('mrclick', hdlClick)
}, 3000)
elBtn.onclick = function () {
eb.emit('mrclick', 'tom', 18)
}
Promise
- MrPromise:
返回:
,手写Promise- 实例方法
- then(onFulfilled, onRejected):
返回:
,then方法 - catch(onRejected):
返回:
,catch方法 - finally(onFinally):
返回:
,finally方法 - 类方法
- resolve(value):
返回:
, - reject(reason):
返回:
, - all(promises):
返回:
, - allSettled(promises):
返回:
, - race(promises):
返回:
, - any(promises):
返回:
,
定义类
js
/**
* 手写Promise
* 1. 基本实现
* 2. 类方法-resolve
* 3. 类方法-reject
* 4. 类方法-all
* 5. 类方法-allSettled
* 6. 类方法-race
* 7. 类方法-any
* 8. 异步延迟调用
* 9. 链式调用
* 10. catch方法
* 11. finally方法
*/
/* 工具函数-封装try...catch函数 */
function runFunctionWithCatchError(fn, value, resolve, reject) {
try {
resolve(fn(value))
} catch (err) {
reject(err)
}
}
// Promise状态
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'
class MrPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledFns = []
this.onRejectedFns = []
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED
this.value = value
for (const fn of this.onFulfilledFns) {
fn(this.value)
}
})
}
}
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
for (const fn of this.onRejectedFns) {
fn(this.reason)
}
})
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
// 判断onFulfilled、onRejected回调函数是否存在
onRejected =
onRejected ||
((err) => {
throw err
})
onFulfilled = onFulfilled || ((res) => res)
return new MrPromise((resolve, reject) => {
// console.log('then status: ', this.status)
if (this.status === PROMISE_STATUS_FULFILLED) {
runFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
}
if (this.status === PROMISE_STATUS_REJECTED) {
runFunctionWithCatchError(onRejected, this.reason, resolve, reject)
}
if (this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFns.push(() => {
runFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
})
this.onRejectedFns.push(() => {
runFunctionWithCatchError(onRejected, this.reason, resolve, reject)
})
}
})
}
catch(onRejected) {
return this.then(undefined, onRejected)
}
finally(onFinally) {
this.then(
() => {
onFinally()
},
() => {
onFinally()
}
)
}
static resolve(value) {
return new Promise((resolve) => resolve(value))
}
static reject(reason) {
return new Promise((resolve, reject) => reject(reason))
}
static all(promises) {
return new Promise((resolve, reject) => {
const values = []
promises.forEach((promise) => {
promise.then(
(res) => {
values.push(res)
if (values.length === promises.length) {
resolve(values)
}
},
(err) => {
reject(err)
}
)
})
})
}
static allSettled(promises) {
return new Promise((resolve, reject) => {
const results = []
promises.forEach((promise) => {
promise.then(
(res) => {
results.push({ status: 'fulfilled', value: res })
if (results.length === promises.length) {
resolve(results)
}
},
(err) => {
results.push({ status: 'rejected', reason: err })
if (results.length === promises.length) {
resolve(results)
}
}
)
})
})
}
static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(
(res) => {
resolve(res)
},
(err) => {
reject(err)
}
)
})
})
}
static any(promises) {
return new Promise((resolve, reject) => {
const reasons = []
promises.forEach((promise) => {
promise.then(
(res) => {
resolve(res)
},
(err) => {
reasons.push(err)
if (reasons.length === promises.length) {
reject(new AggregateError(err))
}
}
)
})
})
}
}
测试
js
// 测试
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p1~')
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p2~')
}, 5000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p3~')
}, 3000)
})
const p = new MrPromise((resolve, reject) => {
// throw new Error('抛出异常')
resolve('aaa')
// reject('111')
// setTimeout(() => {
// // resolve('aaa')
// reject('111')
// }, 1000)
})
// - 类方法-any
Promise.any([p1, p2, p3]).then(
(res) => {
console.log('any res: ', res)
},
(err) => {
console.log('any err: ', err)
}
)
// // - 类方法-race
// Promise.race([p1, p2, p3]).then(
// (res) => {
// console.log('race res: ', res)
// },
// (err) => {
// console.log('race err: ', err)
// }
// )
// // - 类方法-allSettled
// Promise.allSettled([p1, p2, p3]).then((res) => {
// console.log('allSettled: ', res)
// })
// // - 类方法-all
// Promise.all([p1, p2, p3]).then(
// (res) => {
// console.log('all res: ', res)
// },
// (err) => {
// console.log('all err: ', err)
// }
// )
// // - 类方法-reject
// Promise.reject('222').catch((err) => {
// console.log(err)
// })
// // - 类方法-resolve
// Promise.resolve('1111').then((res) => {
// console.log(res)
// })
// p.then(
// (res) => {
// console.log('res: ', res)
// },
// (err) => {
// console.log('err: ', err)
// }
// )
// // - 异步延迟调用
// setTimeout(() => {
// p.then(
// (res) => {
// console.log('异步延时调用 res: ', res)
// },
// (err) => {
// console.log('异步延时调用 err: ', err)
// }
// )
// }, 2000)
// // - 链式调用
// p.then(
// (res) => {
// console.log('链式调用 res1: ', res)
// return 'bbb'
// },
// (err) => {
// console.log('链式调用 err1: ', err)
// return '222'
// }
// ).then(
// (res) => {
// console.log('链式调用 res2: ', res)
// return 'ccc'
// },
// (err) => {
// console.log('链式调用 err2: ', err)
// return '333'
// }
// )
// // - catch
// p.then((res) => {
// console.log('then res: ', res)
// }).catch((err) => {
// console.log('catch err: ', err)
// })
// // - finally
// p.then((res) => {
// console.log('then res: ', res)
// })
// .catch((err) => {
// console.log('catch err: ', err)
// })
// .finally(() => {
// console.log('finally~')
// })
// p.then(
// (res) => {
// console.log('res: ', res)
// },
// (err) => {
// console.log('err: ', err)
// }
// )
// p.then(
// (res) => {
// console.log('res2: ', res)
// },
// (err) => {
// console.log('err2: ', err)
// }
// )
响应式【】
- :
返回:
,
网络请求
axios封装【】
- :
返回:
,